﻿package de.gameduell.framework.game.billard.model {
	import de.gameduell.framework.debug.Debug;
	import de.gameduell.framework.game.billard.event.ShootDataEvent;
	import de.gameduell.framework.util.Tools;
	
	import org.cove.ape.Matrix3f;
	import org.cove.ape.Vector;
	import org.cove.ape.Vector3f;
	
	import flash.events.EventDispatcher;	

	/**
	 * @author cbra
	 */
	public class ShootData extends EventDispatcher {
		public static const DEFAULT_DIRECTION:Vector = new Vector(1,0);
		
		// head shot angle rangle
		//public static const HEAD_SHOOT_ANGLE_RANGE:Number = 1;
		
		private var _strength:Number;
		
		private var _effet:Vector3f;
		private var _direction:Vector;
		private var _velocity:Vector;
		private var _opponentVelocity:Vector;
		private var _hitPoint:Vector;
		
		private var _angle:Number;
		private var settings:BillardSettings;
		private var _strengthNormalized:Number;
		private var _duration:int;

		
		public function ShootData(aSettings:BillardSettings) {
			Debug.trace("ShootData:ShootData");
			settings = aSettings;
			reset();
		}	
		
		public function reset():void{
			_strengthNormalized = 0.5;
			_strength  = calcExponentialStrength(_strengthNormalized);		
			_effet     = new Vector3f();
			_velocity  = new Vector();
			_angle     = 0;
			_hitPoint  = new Vector();
			direction = DEFAULT_DIRECTION;
			
			// make values fixed for network
			toFixed();
		}
		
		public function set duration(aDuration:int):void{
			_duration = aDuration;
		}
		
		public function set opponentVelocity(aOpponentVelocity:Vector):void{
			_opponentVelocity = aOpponentVelocity;
		}

		public function set direction(aDirection:Vector):void {
			_direction = aDirection.clone();
			calcVelocityAndEffet();
			dispatchEvent(new ShootDataEvent(ShootDataEvent.DIRECTION_CHANGED));
			dispatchEvent(new ShootDataEvent(ShootDataEvent.CHANGE));
		}
		
		public function set directionX(aDirectionX:Number):void {
			_direction.x = aDirectionX;
			calcVelocityAndEffet();
			dispatchEvent(new ShootDataEvent(ShootDataEvent.DIRECTION_CHANGED));
			dispatchEvent(new ShootDataEvent(ShootDataEvent.CHANGE));
		}
		
		public function set directionY(aDirectionY:Number):void {
			_direction.y = aDirectionY;
			calcVelocityAndEffet();
			dispatchEvent(new ShootDataEvent(ShootDataEvent.DIRECTION_CHANGED));
			dispatchEvent(new ShootDataEvent(ShootDataEvent.CHANGE));
		}	

		public function set strength(aStrength:Number):void {
			_strength = calcExponentialStrength(aStrength);
//			_strength = aStrength * settings.getValue(BillardSettings.KEY_MAX_SPEED);
			_strengthNormalized = aStrength;
			
//			_strength = aStrength;
			calcVelocityAndEffet();
			dispatchEvent(new ShootDataEvent(ShootDataEvent.STRENGTH_CHANGED));
			dispatchEvent(new ShootDataEvent(ShootDataEvent.CHANGE));
		}

		// aHitPoint is normalized
		public function set hitPoint(aHitPoint:Vector):void {
			_hitPoint = aHitPoint;
			calcVelocityAndEffet();
			dispatchEvent(new ShootDataEvent(ShootDataEvent.EFFET_CHANGED));
			dispatchEvent(new ShootDataEvent(ShootDataEvent.CHANGE));
		}
		
		public function set hitPointX(aHitPointX:Number):void {
			_hitPoint.x = aHitPointX;
			calcVelocityAndEffet();
			dispatchEvent(new ShootDataEvent(ShootDataEvent.EFFET_CHANGED));
			dispatchEvent(new ShootDataEvent(ShootDataEvent.CHANGE));
		}
		
		public function set hitPointY(aHitPointY:Number):void {
			_hitPoint.y = aHitPointY;
			calcVelocityAndEffet();
			dispatchEvent(new ShootDataEvent(ShootDataEvent.EFFET_CHANGED));
			dispatchEvent(new ShootDataEvent(ShootDataEvent.CHANGE));
		}
		
		public function set angle(aAngle:Number):void {
			_angle = aAngle;
			calcVelocityAndEffet();
			dispatchEvent(new ShootDataEvent(ShootDataEvent.ANGLE_CHANGED));
			dispatchEvent(new ShootDataEvent(ShootDataEvent.CHANGE));
		}

		public function calcVelocityAndEffet():void{				
			
			// make values fixed before calculation
			toFixed();
			
			/////////////////////////////////////////////
			// PREPARE (effet and velocity calculation) 
			/////////////////////////////////////////////
			
			// left or right effet (cw/ccw)?
			var sign:int = (_hitPoint.x > 0 ? -1 : (_hitPoint.x < 0 ? 1 : 0));
			
			// headshoot angle between 0 (0°) and 1 (90°)
			var headShootRatio:Number = settings.getValue(BillardSettings.KEY_HEAD_SHOOT_ANGLE_RANGE) * _angle / 90 ;
			
			// headshoot angle between 0 (90° angle) and 1 (0° angle)
			var inverseHeadShootRatio:Number = Math.min(1.0, 1 - headShootRatio);
			
			//////////////////////////////////////////////////////////
			// VELOCITY CALCULATION (depends on hitpoint at effetView)
			//////////////////////////////////////////////////////////

			// fowardshift (decreasing with growing headshoot angle)
			var forwardVelocity:Vector = _direction.mult(_strength).mult(inverseHeadShootRatio);

			// minimal speed on headshoot (avoid, that complete velocity is zero on nearly 90° headshoot)
			var minimalSpeed:Vector = _direction.mult(settings.getValue(BillardSettings.KEY_HEAD_SHOOT_MINIMAL_SPEED)).mult(headShootRatio);
			
			// sideshift (increasing with growing headshoot angle)
			var sideDrift:Vector = calcHitPointPointDriftDirection();
			var sideVelocity:Vector  = sideDrift.mult(headShootRatio * settings.getValue(BillardSettings.KEY_HEAD_SHOOT_TRANSLATION_DRIFT));
			
			// set combined velocity
			_velocity = forwardVelocity.plus(sideVelocity).plus(minimalSpeed);
			
			// limit speed to max
			var maxSpeed:Number = settings.getValue(BillardSettings.KEY_MAX_SPEED);
			if (_velocity.magnitude() > maxSpeed) {
				_velocity = _velocity.normalize().mult(maxSpeed);
			}
			
//			Debug.trace("forwardVelocity: " + forwardVelocity.magnitude() + " minimalSpeed:" + minimalSpeed.magnitude() + " sideVelocity:" + sideVelocity.magnitude());
			
			//////////////////////
			// EFFEE CALCULATION
			//////////////////////
			
			// transform direction 2d to 3d 		
			var direction3D:Vector3f = new Vector3f(direction.x,direction.y,0);	

			// transform contactpoint from effet view camera into world coordinates
			var contactPoint:Vector3f = new Vector3f(_hitPoint.x, _hitPoint.y, 0);		
			var effetViewRotationsMatrix:Matrix3f = new Matrix3f();
			var effetViewAngle:Number = -Math.atan2(direction.x, direction.y);
			effetViewRotationsMatrix.fromAngleAxis(effetViewAngle, Vector3f.UNIT_Y);
			contactPoint = effetViewRotationsMatrix.multVector(contactPoint);
			contactPoint = new Vector3f(-contactPoint.x,contactPoint.z,contactPoint.y);	

			// set base effet			
			_effet = direction3D.crossVector(contactPoint);
			_effet = _effet.multScalar(calcEffetStrength() *_strength * settings.getValue(BillardSettings.KEY_MAX_EFFET));
			_effet.z = -_effet.z; // invert z-Spin
			
			// add ortho effet (increasing with growing headshoot angle)
			var orthoEffet:Vector = direction.mult(sign * settings.getValue(BillardSettings.KEY_HEAD_SHOOT_TRANSLATION_TO_ROTATION_CONVERSION) * headShootRatio);
			_effet.x += orthoEffet.x; 
			_effet.y += orthoEffet.y;

			// limit effet to max
			var maxSpin:Number = settings.getValue(BillardSettings.KEY_MAX_SPIN);
			if (_effet.length() > maxSpin) {
				_effet = _effet.normalize().multScalar(maxSpin);
			}

//			Debug.trace("_effet: "+_effet + " _velocity: " + _velocity + " contactPoint:" + contactPoint + " _hitPoint:" + _hitPoint);

			// make values fixed after calculation
			toFixed();
		}

		private function toFixed():void {
			_strengthNormalized = Tools.toFixedNumber(_strengthNormalized);
			_strength = Tools.toFixedNumber(_strength);
			_effet.x = Tools.toFixedNumber(_effet.x);
			_effet.y = Tools.toFixedNumber(_effet.y);
			_effet.z = Tools.toFixedNumber(_effet.z);
			_direction.x = Tools.toFixedNumber(_direction.x);
			_direction.y = Tools.toFixedNumber(_direction.y);
			_velocity.x = Tools.toFixedNumber(_velocity.x);
			_velocity.y = Tools.toFixedNumber(_velocity.y);
//			_opponentVelocity.x = toFixedNumber(_opponentVelocity.x);
//			_opponentVelocity.y = toFixedNumber(_opponentVelocity.y);
			_hitPoint.x = Tools.toFixedNumber(_hitPoint.x);
			_hitPoint.y = Tools.toFixedNumber(_hitPoint.y);
			_angle = Tools.toFixedNumber(_angle);
		} 

//		private function toFixedNumber(value:Number):Number {
//			return Number(value.toFixed(4));
//		} 
		
		public function calcHitPointPointDriftDirection():Vector {
				// hit_point.x = {-1 .. 0 .. +1} => scale to {-Pi/2, +Pi/2}
				var sideAngle:Number = _hitPoint.x * Math.PI / 2;
				var driftDirection:Vector = _direction.rotate(sideAngle);
				return driftDirection;
		}
		
		public function calcEffetStrength():Number{
			var squared:Number = _hitPoint.magnitude()*_hitPoint.magnitude();
			var capped:Number = Math.min(0.5,squared);
			var scaled:Number = capped * 2;
			return scaled;
		}
		
		private function calcExponentialStrength(linearNormalizedValue:Number):Number{
			var exponentialNormalizedValue:Number = Math.pow(linearNormalizedValue,1.7);
			var exponentialScaledValue:Number = settings.getValue(BillardSettings.KEY_MAX_SPEED)* exponentialNormalizedValue;
			return exponentialScaledValue;
		}
		
		override public function toString():String{
			return "ShootData: direction "+direction+" hitPoint "+hitPoint+ " strength "+ strength + " angle "+angle;
		}
		
		public function get duration():int{
			return _duration;
		}
		
		public function get hitPoint():Vector{
			return _hitPoint;
		}	
		
		public function get hitPointX():Number{
			return _hitPoint.x;
		}
		
		public function get hitPointY():Number{
			return _hitPoint.x;
		}
		
		public function get angle():Number{
			return _angle;
		}

		public function get direction():Vector{
			return _direction.clone();
		}
		
		public function get directionX():Number{
			return _direction.x;
		}
		
		public function get directionY():Number{
			return _direction.y;
		}
		
		public function get velocity():Vector{
			return _velocity.clone();
		}
		
		public function get opponentVelocity():Vector{
			return _opponentVelocity.clone();
		}
		
		public function get strength():Number{
			return _strengthNormalized;
		}
		
		public function get effet():Vector3f{
			return _effet.clone();
		}
	}
}
